const { ScopedRequest } = require('scopedrequest')
const dotenv = require('dotenv')
const axios = require('axios')
const path = require('path')
const { URL } = require('url')
const { formatDate, assign, parse } = require('ts-fns')
const formidable = require('formidable')
const fs = require('fs')
const FormData = require('form-data')
const UglifyJS = require('uglify-js');
const { ScopeX } = require('scopex');

dotenv.config()

const {
  BASE_URL,
  HOST = '127.0.0.1',
  PORT = 9000,
  REQUEST_URI = '/request',
  SDK_URI = '/sdk.js',
} = process.env


function isFile(node) {
  return node.mimetype && node.filepath
}
function objectToFormData(obj, form, namespace) {
  var fd = form || {};
  var formKey;

  for (var property in obj) {
    if (!Object.prototype.hasOwnProperty.call(obj, property)) {
      continue
    }
    var key = Array.isArray(obj) ? '[]' : `[${property}]`;
    var node = obj[property];

    if (namespace) {
      formKey = namespace + key;
    }
    else {
      formKey = property;
    }

    if (node && typeof node === 'object' && isFile(node)) {
      fd[formKey] = node
    }
    // if the property is an object, but not a File, use recursivity.
    else if (node && typeof obj[property] === 'object') {
      objectToFormData(obj[property], fd, formKey);
    }
    else {
      fd[formKey] = node;
    }
  }
  return fd;
}
function containsContentType(req, type) {
  const contentType = req.get('content-type') || 'text/plain'
  return contentType.indexOf(type) > -1
}
function fetch(to, config, data, req) {
  const { headers: requestHeaders } = req
  const { headers: commandHeaders = {}, method } = config

  const baseUrl = BASE_URL ? BASE_URL : `http://${HOST}:${PORT}`
  const url = to[0] === '/' ? baseUrl + to : to

  const u = new URL(url)
  const { host } = u
  const href = u.toString()

  const headers = {
    ...requestHeaders,
    host,
  }

  Object.assign(headers, commandHeaders)

  // 会引起问题，因此删掉
  delete headers['content-length']

  const fn = method ? method.toLowerCase() : 'get'

  if (fn && (fn === 'post' || fn === 'put' || fn === 'patch')) {
    if (containsContentType(req, 'multipart/form-data')) {
      const flat = objectToFormData(data)
      const formdata = new FormData()
      Object.keys(flat).forEach((key) => {
        const value = flat[key]
        if (isFile(value)) {
          const { filepath } = value
          const stream = fs.createReadStream(filepath)
          formdata.append(key, stream)
        }
        else {
          formdata.append(key, value)
        }
      })
      Object.assign(headers, formdata.getHeaders())
      return axios[fn](href, formdata, { headers }).then(res => res.data)
    }

    return axios[fn](href, data, { headers }).then(res => res.data)
  }

  if (!axios[fn]) {
    return Promise.reject(new Error(`不支持${fn}命令`))
  }

  return axios[fn](href, { headers }).then(res => res.data).catch(e => console.log(e.message, e.stack))
}

const query = new ScopedRequest({
  scopex: ScopeX,
  shapes: {
    ARRAY_FILTER: (cexp) => (arr) => {
      if (!Array.isArray(arr)) {
        return arr
      }
      const exps = cexp.split('&').map((item) => {
        const [, key, compare, value] = item.match(/^(.*?)(==|<|>|<=|>=)(.*?)$/)
        const v = tryJsonParse(value)
        const to = typeof v === 'string' ? JSON.stringify(v) : v
        return { key, compare, value: to }
      })
      const outs = arr.filter((item) => {
        if (typeof item !== 'object') {
          return false
        }
        const scopex = new ScopeX(item)
        for (let i = 0, len = exps.length; i < len; i ++) {
          const { key, compare, value } = exps[i]
          const bool = scopex.parse(`${key}${compare}${value}`)
          if (!bool) {
            return false
          }
        }
        return true
      })
      return outs
    },
  },
  fetch,
  debug(e) {
    console.warn(e)
  },
})

function tryJsonParse(str) {
  try {
    return JSON.parse(str)
  }
  catch (e) {
    return str
  }
}

function createRequest(fn)  {
  return (req, res) => {
    const contiansType = (type) => containsContentType(req, type)
    const emitError = (error, message = '请求错误', code = 500) => {
      res.status(code)
      res.send({ code, message })
      console.error(formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss'), error)
    }
    const { body } = req

    let program = ''
    let dataList = null

    const run = () => {
      query[fn](program, null, dataList, req).then((data) => {
        res.json(data)
      }).catch((error) => {
        emitError(error)
      })
    }

    if (contiansType('text/plain')) {
      program = body
      run()
    }
    /**
     * 发送过来的请求体必需是 {
     *   _: 'program代码',
     *   // 后面的对应代码中的请求体
     *   0: {},
     *   1: [],
     * }
     */
    else if (contiansType('application/json') || contiansType('application/x-www-form-urlencoded')) {
      const { _, ...list } = body
      program = _
      dataList = []
      const indexes = Object.keys(list)
      indexes.forEach((i) => {
        const data = list[i]
        dataList[i] = data
      })
      run()
    }
    else if (contiansType('multipart/form-data')) {
      const form = formidable({ multiples: true })
      form.parse(req, (err, fields, files) => {
        if (err) {
          emitError(err)
          return
        }

        const { _, ...list } = fields
        const fileKeys = Object.keys(files)
        fileKeys.forEach((key) => {
          const file = files[key]
          assign(list, key, file)
        })

        program = _
        dataList = []
        const indexes = Object.keys(list)
        indexes.forEach((i) => {
          const data = list[i]
          dataList[i] = data
        })
        run()
      })
    }
    else {
      const message = '不支持当前请求的文档类型'
      emitError(new Error(message), message)
    }
  }
}

function createSDK() {
  return (req, res) => {
    const contents = fs.readFileSync(path.resolve(__dirname, 'sdk.js')).toString()
    const js = contents.replace('REQUEST_URI', JSON.stringify(REQUEST_URI)).replace('SDK_URI', JSON.stringify(SDK_URI))
    const uglify = UglifyJS.minify(js);
    res.set('Content-Type', 'text/javascript;charset=UTF-8')
    res.send(uglify.code)
  }
}

module.exports = {
  createRequest,
  createSDK,
  containsContentType,
}
